home *** CD-ROM | disk | FTP | other *** search
/ Game.EXE 2001 January / Game.EXE_01_2001.iso / demos / Blade of Darkness / data1.cab / Program_Executable_Files / Lib / PythonLib / nntplib.py < prev    next >
Encoding:
Python Source  |  2000-11-16  |  13.0 KB  |  479 lines

  1. # An NNTP client class.  Based on RFC 977: Network News Transfer
  2. # Protocol, by Brian Kantor and Phil Lapsley.
  3.  
  4.  
  5. # Example:
  6. #
  7. # >>> from nntplib import NNTP
  8. # >>> s = NNTP('news')
  9. # >>> resp, count, first, last, name = s.group('comp.lang.python')
  10. # >>> print 'Group', name, 'has', count, 'articles, range', first, 'to', last
  11. # Group comp.lang.python has 51 articles, range 5770 to 5821
  12. # >>> resp, subs = s.xhdr('subject', first + '-' + last)
  13. # >>> resp = s.quit()
  14. # >>>
  15. #
  16. # Here 'resp' is the server response line.
  17. # Error responses are turned into exceptions.
  18. #
  19. # To post an article from a file:
  20. # >>> f = open(filename, 'r') # file containing article, including header
  21. # >>> resp = s.post(f)
  22. # >>>
  23. #
  24. # For descriptions of all methods, read the comments in the code below.
  25. # Note that all arguments and return values representing article numbers
  26. # are strings, not numbers, since they are rarely used for calculations.
  27.  
  28. # (xover, xgtitle, xpath, date methods by Kevan Heydon)
  29.  
  30.  
  31. # Imports
  32. import re
  33. import socket
  34. import string
  35.  
  36.  
  37. # Exception raised when an error or invalid response is received
  38.  
  39. error_reply = 'nntplib.error_reply'    # unexpected [123]xx reply
  40. error_temp = 'nntplib.error_temp'    # 4xx errors
  41. error_perm = 'nntplib.error_perm'    # 5xx errors
  42. error_proto = 'nntplib.error_proto'    # response does not begin with [1-5]
  43. error_data = 'nntplib.error_data'    # error in response data
  44.  
  45.  
  46. # Standard port used by NNTP servers
  47. NNTP_PORT = 119
  48.  
  49.  
  50. # Response numbers that are followed by additional text (e.g. article)
  51. LONGRESP = ['100', '215', '220', '221', '222', '224', '230', '231', '282']
  52.  
  53.  
  54. # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
  55. CRLF = '\r\n'
  56.  
  57.  
  58. # The class itself
  59.  
  60. class NNTP:
  61.  
  62.     # Initialize an instance.  Arguments:
  63.     # - host: hostname to connect to
  64.     # - port: port to connect to (default the standard NNTP port)
  65.  
  66.     def __init__(self, host, port = NNTP_PORT, user=None, password=None):
  67.         self.host = host
  68.         self.port = port
  69.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  70.         self.sock.connect(self.host, self.port)
  71.         self.file = self.sock.makefile('rb')
  72.         self.debugging = 0
  73.         self.welcome = self.getresp()
  74.         if user:
  75.             resp = self.shortcmd('authinfo user '+user)
  76.             if resp[:3] == '381':
  77.                 if not password:
  78.                     raise error_reply, resp
  79.                 else:
  80.                     resp = self.shortcmd(
  81.                         'authinfo pass '+password)
  82.                     if resp[:3] != '281':
  83.                         raise error_perm, resp
  84.  
  85.     # Get the welcome message from the server
  86.     # (this is read and squirreled away by __init__()).
  87.     # If the response code is 200, posting is allowed;
  88.     # if it 201, posting is not allowed
  89.  
  90.     def getwelcome(self):
  91.         if self.debugging: print '*welcome*', `self.welcome`
  92.         return self.welcome
  93.  
  94.     # Set the debugging level.  Argument level means:
  95.     # 0: no debugging output (default)
  96.     # 1: print commands and responses but not body text etc.
  97.     # 2: also print raw lines read and sent before stripping CR/LF
  98.  
  99.     def set_debuglevel(self, level):
  100.         self.debugging = level
  101.     debug = set_debuglevel
  102.  
  103.     # Internal: send one line to the server, appending CRLF
  104.     def putline(self, line):
  105.         line = line + CRLF
  106.         if self.debugging > 1: print '*put*', `line`
  107.         self.sock.send(line)
  108.  
  109.     # Internal: send one command to the server (through putline())
  110.     def putcmd(self, line):
  111.         if self.debugging: print '*cmd*', `line`
  112.         self.putline(line)
  113.  
  114.     # Internal: return one line from the server, stripping CRLF.
  115.     # Raise EOFError if the connection is closed
  116.     def getline(self):
  117.         line = self.file.readline()
  118.         if self.debugging > 1:
  119.             print '*get*', `line`
  120.         if not line: raise EOFError
  121.         if line[-2:] == CRLF: line = line[:-2]
  122.         elif line[-1:] in CRLF: line = line[:-1]
  123.         return line
  124.  
  125.     # Internal: get a response from the server.
  126.     # Raise various errors if the response indicates an error
  127.     def getresp(self):
  128.         resp = self.getline()
  129.         if self.debugging: print '*resp*', `resp`
  130.         c = resp[:1]
  131.         if c == '4':
  132.             raise error_temp, resp
  133.         if c == '5':
  134.             raise error_perm, resp
  135.         if c not in '123':
  136.             raise error_proto, resp
  137.         return resp
  138.  
  139.     # Internal: get a response plus following text from the server.
  140.     # Raise various errors if the response indicates an error
  141.     def getlongresp(self):
  142.         resp = self.getresp()
  143.         if resp[:3] not in LONGRESP:
  144.             raise error_reply, resp
  145.         list = []
  146.         while 1:
  147.             line = self.getline()
  148.             if line == '.':
  149.                 break
  150.             if line[:2] == '..':
  151.                 line = line[1:]
  152.             list.append(line)
  153.         return resp, list
  154.  
  155.     # Internal: send a command and get the response
  156.     def shortcmd(self, line):
  157.         self.putcmd(line)
  158.         return self.getresp()
  159.  
  160.     # Internal: send a command and get the response plus following text
  161.     def longcmd(self, line):
  162.         self.putcmd(line)
  163.         return self.getlongresp()
  164.  
  165.     # Process a NEWGROUPS command.  Arguments:
  166.     # - date: string 'yymmdd' indicating the date
  167.     # - time: string 'hhmmss' indicating the time
  168.     # Return:
  169.     # - resp: server response if succesful
  170.     # - list: list of newsgroup names
  171.  
  172.     def newgroups(self, date, time):
  173.         return self.longcmd('NEWGROUPS ' + date + ' ' + time)
  174.  
  175.     # Process a NEWNEWS command.  Arguments:
  176.     # - group: group name or '*'
  177.     # - date: string 'yymmdd' indicating the date
  178.     # - time: string 'hhmmss' indicating the time
  179.     # Return:
  180.     # - resp: server response if succesful
  181.     # - list: list of article ids
  182.  
  183.     def newnews(self, group, date, time):
  184.         cmd = 'NEWNEWS ' + group + ' ' + date + ' ' + time
  185.         return self.longcmd(cmd)
  186.  
  187.     # Process a LIST command.  Return:
  188.     # - resp: server response if succesful
  189.     # - list: list of (group, last, first, flag) (strings)
  190.  
  191.     def list(self):
  192.         resp, list = self.longcmd('LIST')
  193.         for i in range(len(list)):
  194.             # Parse lines into "group last first flag"
  195.             list[i] = tuple(string.split(list[i]))
  196.         return resp, list
  197.  
  198.     # Process a GROUP command.  Argument:
  199.     # - group: the group name
  200.     # Returns:
  201.     # - resp: server response if succesful
  202.     # - count: number of articles (string)
  203.     # - first: first article number (string)
  204.     # - last: last article number (string)
  205.     # - name: the group name
  206.  
  207.     def group(self, name):
  208.         resp = self.shortcmd('GROUP ' + name)
  209.         if resp[:3] <> '211':
  210.             raise error_reply, resp
  211.         words = string.split(resp)
  212.         count = first = last = 0
  213.         n = len(words)
  214.         if n > 1:
  215.             count = words[1]
  216.             if n > 2:
  217.                 first = words[2]
  218.                 if n > 3:
  219.                     last = words[3]
  220.                     if n > 4:
  221.                         name = string.lower(words[4])
  222.         return resp, count, first, last, name
  223.  
  224.     # Process a HELP command.  Returns:
  225.     # - resp: server response if succesful
  226.     # - list: list of strings
  227.  
  228.     def help(self):
  229.         return self.longcmd('HELP')
  230.  
  231.     # Internal: parse the response of a STAT, NEXT or LAST command
  232.     def statparse(self, resp):
  233.         if resp[:2] <> '22':
  234.             raise error_reply, resp
  235.         words = string.split(resp)
  236.         nr = 0
  237.         id = ''
  238.         n = len(words)
  239.         if n > 1:
  240.             nr = words[1]
  241.             if n > 2:
  242.                 id = string.lower(words[2])
  243.         return resp, nr, id
  244.  
  245.     # Internal: process a STAT, NEXT or LAST command
  246.     def statcmd(self, line):
  247.         resp = self.shortcmd(line)
  248.         return self.statparse(resp)
  249.  
  250.     # Process a STAT command.  Argument:
  251.     # - id: article number or message id
  252.     # Returns:
  253.     # - resp: server response if succesful
  254.     # - nr:   the article number
  255.     # - id:   the article id
  256.  
  257.     def stat(self, id):
  258.         return self.statcmd('STAT ' + id)
  259.  
  260.     # Process a NEXT command.  No arguments.  Return as for STAT
  261.  
  262.     def next(self):
  263.         return self.statcmd('NEXT')
  264.  
  265.     # Process a LAST command.  No arguments.  Return as for STAT
  266.  
  267.     def last(self):
  268.         return self.statcmd('LAST')
  269.  
  270.     # Internal: process a HEAD, BODY or ARTICLE command
  271.     def artcmd(self, line):
  272.         resp, list = self.longcmd(line)
  273.         resp, nr, id = self.statparse(resp)
  274.         return resp, nr, id, list
  275.  
  276.     # Process a HEAD command.  Argument:
  277.     # - id: article number or message id
  278.     # Returns:
  279.     # - resp: server response if succesful
  280.     # - list: the lines of the article's header
  281.  
  282.     def head(self, id):
  283.         return self.artcmd('HEAD ' + id)
  284.  
  285.     # Process a BODY command.  Argument:
  286.     # - id: article number or message id
  287.     # Returns:
  288.     # - resp: server response if succesful
  289.     # - list: the lines of the article's body
  290.  
  291.     def body(self, id):
  292.         return self.artcmd('BODY ' + id)
  293.  
  294.     # Process an ARTICLE command.  Argument:
  295.     # - id: article number or message id
  296.     # Returns:
  297.     # - resp: server response if succesful
  298.     # - list: the lines of the article
  299.  
  300.     def article(self, id):
  301.         return self.artcmd('ARTICLE ' + id)
  302.  
  303.     # Process a SLAVE command.  Returns:
  304.     # - resp: server response if succesful
  305.  
  306.     def slave(self):
  307.         return self.shortcmd('SLAVE')
  308.  
  309.     # Process an XHDR command (optional server extension).  Arguments:
  310.     # - hdr: the header type (e.g. 'subject')
  311.     # - str: an article nr, a message id, or a range nr1-nr2
  312.     # Returns:
  313.     # - resp: server response if succesful
  314.     # - list: list of (nr, value) strings
  315.  
  316.     def xhdr(self, hdr, str):
  317.         pat = re.compile('^([0-9]+) ?(.*)\n?')
  318.         resp, lines = self.longcmd('XHDR ' + hdr + ' ' + str)
  319.         for i in range(len(lines)):
  320.             line = lines[i]
  321.             m = pat.match(line)
  322.             if m:
  323.                 lines[i] = m.group(1, 2)
  324.         return resp, lines
  325.  
  326.     # Process an XOVER command (optional server extension) Arguments:
  327.     # - start: start of range
  328.     # - end: end of range
  329.     # Returns:
  330.     # - resp: server response if succesful
  331.     # - list: list of (art-nr, subject, poster, date, id, refrences, size, lines)
  332.  
  333.     def xover(self,start,end):
  334.         resp, lines = self.longcmd('XOVER ' + start + '-' + end)
  335.         xover_lines = []
  336.         for line in lines:
  337.             elem = string.splitfields(line,"\t")
  338.             try:
  339.                 xover_lines.append((elem[0],
  340.                             elem[1],
  341.                             elem[2],
  342.                             elem[3],
  343.                             elem[4],
  344.                             string.split(elem[5]),
  345.                             elem[6],
  346.                             elem[7]))
  347.             except IndexError:
  348.                 raise error_data,line
  349.         return resp,xover_lines
  350.  
  351.     # Process an XGTITLE command (optional server extension) Arguments:
  352.     # - group: group name wildcard (i.e. news.*)
  353.     # Returns:
  354.     # - resp: server response if succesful
  355.     # - list: list of (name,title) strings
  356.  
  357.     def xgtitle(self, group):
  358.         line_pat = re.compile("^([^ \t]+)[ \t]+(.*)$")
  359.         resp, raw_lines = self.longcmd('XGTITLE ' + group)
  360.         lines = []
  361.         for raw_line in raw_lines:
  362.             match = line_pat.search(string.strip(raw_line))
  363.             if match:
  364.                 lines.append(match.group(1, 2))
  365.         return resp, lines
  366.  
  367.     # Process an XPATH command (optional server extension) Arguments:
  368.     # - id: Message id of article
  369.     # Returns:
  370.     # resp: server response if succesful
  371.     # path: directory path to article
  372.  
  373.     def xpath(self,id):
  374.         resp = self.shortcmd("XPATH " + id)
  375.         if resp[:3] <> '223':
  376.             raise error_reply, resp
  377.         try:
  378.             [resp_num, path] = string.split(resp)
  379.         except ValueError:
  380.             raise error_reply, resp
  381.         else:
  382.             return resp, path
  383.  
  384.     # Process the DATE command. Arguments:
  385.     # None
  386.     # Returns:
  387.     # resp: server response if succesful
  388.     # date: Date suitable for newnews/newgroups commands etc.
  389.     # time: Time suitable for newnews/newgroups commands etc.
  390.  
  391.     def date (self):
  392.         resp = self.shortcmd("DATE")
  393.         if resp[:3] <> '111':
  394.             raise error_reply, resp
  395.         elem = string.split(resp)
  396.         if len(elem) != 2:
  397.             raise error_data, resp
  398.         date = elem[1][2:8]
  399.         time = elem[1][-6:]
  400.         if len(date) != 6 or len(time) != 6:
  401.             raise error_data, resp
  402.         return resp, date, time
  403.  
  404.  
  405.     # Process a POST command.  Arguments:
  406.     # - f: file containing the article
  407.     # Returns:
  408.     # - resp: server response if succesful
  409.  
  410.     def post(self, f):
  411.         resp = self.shortcmd('POST')
  412.         # Raises error_??? if posting is not allowed
  413.         if resp[0] <> '3':
  414.             raise error_reply, resp
  415.         while 1:
  416.             line = f.readline()
  417.             if not line:
  418.                 break
  419.             if line[-1] == '\n':
  420.                 line = line[:-1]
  421.             if line[:1] == '.':
  422.                 line = '.' + line
  423.             self.putline(line)
  424.         self.putline('.')
  425.         return self.getresp()
  426.  
  427.     # Process an IHAVE command.  Arguments:
  428.     # - id: message-id of the article
  429.     # - f:  file containing the article
  430.     # Returns:
  431.     # - resp: server response if succesful
  432.     # Note that if the server refuses the article an exception is raised
  433.  
  434.     def ihave(self, id, f):
  435.         resp = self.shortcmd('IHAVE ' + id)
  436.         # Raises error_??? if the server already has it
  437.         if resp[0] <> '3':
  438.             raise error_reply, resp
  439.         while 1:
  440.             line = f.readline()
  441.             if not line:
  442.                 break
  443.             if line[-1] == '\n':
  444.                 line = line[:-1]
  445.             if line[:1] == '.':
  446.                 line = '.' + line
  447.             self.putline(line)
  448.         self.putline('.')
  449.         return self.getresp()
  450.  
  451.      # Process a QUIT command and close the socket.  Returns:
  452.      # - resp: server response if succesful
  453.  
  454.     def quit(self):
  455.         resp = self.shortcmd('QUIT')
  456.         self.file.close()
  457.         self.sock.close()
  458.         del self.file, self.sock
  459.         return resp
  460.  
  461.  
  462. # Minimal test function
  463. def _test():
  464.     s = NNTP('news')
  465.     resp, count, first, last, name = s.group('comp.lang.python')
  466.     print resp
  467.     print 'Group', name, 'has', count, 'articles, range', first, 'to', last
  468.     resp, subs = s.xhdr('subject', first + '-' + last)
  469.     print resp
  470.     for item in subs:
  471.         print "%7s %s" % item
  472.     resp = s.quit()
  473.     print resp
  474.  
  475.  
  476. # Run the test when run as a script
  477. if __name__ == '__main__':
  478.     _test()
  479.